<!-- directive:breadcrumb ES6学习笔记 -->
<!-- directive:title Class 可以通过extends关键字实现继承 -->
<div class='panel-body'>
    <p>这比 ES5 的通过修改原型链实现继承，要清晰和方便很多。</p>
    <p>class Point {</p>
    <p>}</p>
    <p>class ColorPoint extends Point {</p>
    <p>}</p>
    <p>上面代码定义了一个ColorPoint类，该类通过extends关键字，继承了Point类的所有属性和方法。但是由于没有部署任何代码，所以这两个类完全一样，等于复制了一个Point类。下面，我们在ColorPoint内部加上代码。</p>
    <p>class ColorPoint extends Point {</p>
    <p>    constructor(x, y, color) {</p>
    <p>        super(x, y); // 调用父类的constructor(x, y)</p>
    <p>        this.color = color;</p>
    <p>    }</p>
    <p>    toString() {</p>
    <p>        return this.color + ' ' + super.toString(); // 调用父类的toString()</p>
    <p>    }</p>
    <p>}</p>
    <p>上面代码中，constructor方法和toString方法之中，都出现了super关键字，它在这里表示父类的构造函数，用来新建父类的this对象。</p>
    <p>子类必须在constructor方法中调用super方法，否则新建实例时会报错。这是因为子类自己的this对象，必须先通过父类的构造函数完成塑造，得到与父类同样的实例属性和方法，然后再对其进行加工，加上子类自己的实例属性和方法。如果不调用super方法，子类就得不到this对象。</p>
    <p>class Point { /* ... */ }</p>
    <p>class ColorPoint extends Point {</p>
    <p>    constructor() {</p>
    <p>    }</p>
    <p>}</p>
    <p>let cp = new ColorPoint(); // ReferenceError</p>
    <p>上面代码中，ColorPoint继承了父类Point，但是它的构造函数没有调用super方法，导致新建实例时报错。</p>
    <p>ES5 的继承，实质是先创造子类的实例对象this，然后再将父类的方法添加到this上面（Parent.apply(this)）。ES6 的继承机制完全不同，实质是先将父类实例对象的属性和方法，加到this上面（所以必须先调用super方法），然后再用子类的构造函数修改this。</p>
    <p>如果子类没有定义constructor方法，这个方法会被默认添加，代码如下。也就是说，不管有没有显式定义，任何一个子类都有constructor方法。</p>
    <p>class ColorPoint extends Point {</p>
    <p>}</p>
    <p>// 等同于</p>
    <p>class ColorPoint extends Point {</p>
    <p>    constructor(...args) {</p>
    <p>        super(...args);</p>
    <p>    }</p>
    <p>}</p>
    <p>另一个需要注意的地方是，在子类的构造函数中，只有调用super之后，才可以使用this关键字，否则会报错。这是因为子类实例的构建，基于父类实例，只有super方法才能调用父类实例。</p>
    <p>class Point {</p>
    <p>    constructor(x, y) {</p>
    <p>        this.x = x;</p>
    <p>        this.y = y;</p>
    <p>    }</p>
    <p>}</p>
    <p>class ColorPoint extends Point {</p>
    <p>    constructor(x, y, color) {</p>
    <p>        this.color = color; // ReferenceError</p>
    <p>        super(x, y);</p>
    <p>        this.color = color; // 正确</p>
    <p>    }</p>
    <p>}</p>
    <p>上面代码中，子类的constructor方法没有调用super之前，就使用this关键字，结果报错，而放在super方法之后就是正确的。</p>
    <p>下面是生成子类实例的代码。</p>
    <p>let cp = new ColorPoint(25, 8, 'green');</p>
    <p>cp instanceof ColorPoint // true</p>
    <p>cp instanceof Point // true</p>
    <p>上面代码中，实例对象cp同时是ColorPoint和Point两个类的实例，这与 ES5 的行为完全一致。</p>
    <p>最后，父类的静态方法，也会被子类继承。</p>
    <p>class A {</p>
    <p>    static hello() {</p>
    <p>        console.log('hello world');</p>
    <p>    }</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>}</p>
    <p>B.hello()    // hello world</p>
    <p>上面代码中，hello()是A类的静态方法，B继承A，也继承了A的静态方法。</p>
    <p>Object.getPrototypeOf()</p>
    <p>Object.getPrototypeOf方法可以用来从子类上获取父类。</p>
    <p>Object.getPrototypeOf(ColorPoint) === Point</p>
    <p>// true</p>
    <p>因此，可以使用这个方法判断，一个类是否继承了另一个类。</p>
    <p>super 关键字</p>
    <p>super这个关键字，既可以当作函数使用，也可以当作对象使用。在这两种情况下，它的用法完全不同。</p>
    <p>第一种情况，super作为函数调用时，代表父类的构造函数。ES6 要求，子类的构造函数必须执行一次super函数。</p>
    <p>class A {}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>    }</p>
    <p>}</p>
    <p>上面代码中，子类B的构造函数之中的super()，代表调用父类的构造函数。这是必须的，否则 JavaScript 引擎会报错。</p>
    <p>注意，super虽然代表了父类A的构造函数，但是返回的是子类B的实例，即super内部的this指的是B的实例，因此super()在这里相当于A.prototype.constructor.call(this)。</p>
    <p>class A {</p>
    <p>    constructor() {</p>
    <p>        console.log(new.target.name);</p>
    <p>    }</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>    }</p>
    <p>}</p>
    <p>new A() // A</p>
    <p>new B() // B</p>
    <p>上面代码中，new.target指向当前正在执行的函数。可以看到，在super()执行时，它指向的是子类B的构造函数，而不是父类A的构造函数。也就是说，super()内部的this指向的是B。</p>
    <p>作为函数时，super()只能用在子类的构造函数之中，用在其他地方就会报错。</p>
    <p>class A {}</p>
    <p>class B extends A {</p>
    <p>    m() {</p>
    <p>        super(); // 报错</p>
    <p>    }</p>
    <p>}</p>
    <p>上面代码中，super()用在B类的m方法之中，就会造成句法错误。</p>
    <p>第二种情况，super作为对象时，在普通方法中，指向父类的原型对象；在静态方法中，指向父类。</p>
    <p>class A {</p>
    <p>    p() {</p>
    <p>        return 2;</p>
    <p>    }</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        console.log(super.p()); // 2</p>
    <p>    }</p>
    <p>}</p>
    <p>let b = new B();</p>
    <p>上面代码中，子类B当中的super.p()，就是将super当作一个对象使用。这时，super在普通方法之中，指向A.prototype，所以super.p()就相当于A.prototype.p()。</p>
    <p>这里需要注意，由于super指向父类的原型对象，所以定义在父类实例上的方法或属性，是无法通过super调用的。</p>
    <p>class A {</p>
    <p>    constructor() {</p>
    <p>        this.p = 2;</p>
    <p>    }</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>    get m() {</p>
    <p>        return super.p;</p>
    <p>    }</p>
    <p>}</p>
    <p>let b = new B();</p>
    <p>b.m // undefined</p>
    <p>上面代码中，p是父类A实例的属性，super.p就引用不到它。</p>
    <p>如果属性定义在父类的原型对象上，super就可以取到。</p>
    <p>class A {}</p>
    <p>A.prototype.x = 2;</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        console.log(super.x) // 2</p>
    <p>    }</p>
    <p>}</p>
    <p>let b = new B();</p>
    <p>上面代码中，属性x是定义在A.prototype上面的，所以super.x可以取到它的值。</p>
    <p>ES6 规定，在子类普通方法中通过super调用父类的方法时，方法内部的this指向当前的子类实例。</p>
    <p>class A {</p>
    <p>    constructor() {</p>
    <p>        this.x = 1;</p>
    <p>    }</p>
    <p>    print() {</p>
    <p>        console.log(this.x);</p>
    <p>    }</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        this.x = 2;</p>
    <p>    }</p>
    <p>    m() {</p>
    <p>        super.print();</p>
    <p>    }</p>
    <p>}</p>
    <p>let b = new B();</p>
    <p>b.m() // 2</p>
    <p>上面代码中，super.print()虽然调用的是A.prototype.print()，但是A.prototype.print()内部的this指向子类B的实例，导致输出的是2，而不是1。也就是说，实际上执行的是super.print.call(this)。</p>
    <p>由于this指向子类实例，所以如果通过super对某个属性赋值，这时super就是this，赋值的属性会变成子类实例的属性。</p>
    <p>class A {</p>
    <p>    constructor() {</p>
    <p>        this.x = 1;</p>
    <p>    }</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        this.x = 2;</p>
    <p>        super.x = 3;</p>
    <p>        console.log(super.x); // undefined</p>
    <p>        console.log(this.x); // 3</p>
    <p>    }</p>
    <p>}</p>
    <p>let b = new B();</p>
    <p>上面代码中，super.x赋值为3，这时等同于对this.x赋值为3。而当读取super.x的时候，读的是A.prototype.x，所以返回undefined。</p>
    <p>如果super作为对象，用在静态方法之中，这时super将指向父类，而不是父类的原型对象。</p>
    <p>class Parent {</p>
    <p>    static myMethod(msg) {</p>
    <p>        console.log('static', msg);</p>
    <p>    }</p>
    <p>    myMethod(msg) {</p>
    <p>        console.log('instance', msg);</p>
    <p>    }</p>
    <p>}</p>
    <p>class Child extends Parent {</p>
    <p>    static myMethod(msg) {</p>
    <p>        super.myMethod(msg);</p>
    <p>    }</p>
    <p>    myMethod(msg) {</p>
    <p>        super.myMethod(msg);</p>
    <p>    }</p>
    <p>}</p>
    <p>Child.myMethod(1); // static 1</p>
    <p>var child = new Child();</p>
    <p>child.myMethod(2); // instance 2</p>
    <p>上面代码中，super在静态方法之中指向父类，在普通方法之中指向父类的原型对象。</p>
    <p>另外，在子类的静态方法中通过super调用父类的方法时，方法内部的this指向当前的子类，而不是子类的实例。</p>
    <p>class A {</p>
    <p>    constructor() {</p>
    <p>        this.x = 1;</p>
    <p>    }</p>
    <p>    static print() {</p>
    <p>        console.log(this.x);</p>
    <p>    }</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        this.x = 2;</p>
    <p>    }</p>
    <p>    static m() {</p>
    <p>        super.print();</p>
    <p>    }</p>
    <p>}</p>
    <p>B.x = 3;</p>
    <p>B.m() // 3</p>
    <p>上面代码中，静态方法B.m里面，super.print指向父类的静态方法。这个方法里面的this指向的是B，而不是B的实例。</p>
    <p>注意，使用super的时候，必须显式指定是作为函数、还是作为对象使用，否则会报错。</p>
    <p>class A {}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        console.log(super); // 报错</p>
    <p>    }</p>
    <p>}</p>
    <p>上面代码中，console.log(super)当中的super，无法看出是作为函数使用，还是作为对象使用，所以 JavaScript 引擎解析代码的时候就会报错。这时，如果能清晰地表明super的数据类型，就不会报错。</p>
    <p>class A {}</p>
    <p>class B extends A {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        console.log(super.valueOf() instanceof B); // true</p>
    <p>    }</p>
    <p>}</p>
    <p>let b = new B();</p>
    <p>上面代码中，super.valueOf()表明super是一个对象，因此就不会报错。同时，由于super使得this指向B的实例，所以super.valueOf()返回的是一个B的实例。</p>
    <p>最后，由于对象总是继承其他对象的，所以可以在任意一个对象中，使用super关键字。</p>
    <p>var obj = {</p>
    <p>    toString() {</p>
    <p>        return "MyObject: " + super.toString();</p>
    <p>    }</p>
    <p>};</p>
    <p>obj.toString(); // MyObject: [object Object]</p>
    <p>类的 prototype 属性和__proto__属性</p>
    <p>大多数浏览器的 ES5 实现之中，每一个对象都有__proto__属性，指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖，同时有prototype属性和__proto__属性，因此同时存在两条继承链。</p>
    <p>（1）子类的__proto__属性，表示构造函数的继承，总是指向父类。</p>
    <p>（2）子类prototype属性的__proto__属性，表示方法的继承，总是指向父类的prototype属性。</p>
    <p>class A {</p>
    <p>}</p>
    <p>class B extends A {</p>
    <p>}</p>
    <p>B.__proto__ === A // true</p>
    <p>B.prototype.__proto__ === A.prototype // true</p>
    <p>上面代码中，子类B的__proto__属性指向父类A，子类B的prototype属性的__proto__属性指向父类A的prototype属性。</p>
    <p>这样的结果是因为，类的继承是按照下面的模式实现的。</p>
    <p>class A {</p>
    <p>}</p>
    <p>class B {</p>
    <p>}</p>
    <p>// B 的实例继承 A 的实例</p>
    <p>Object.setPrototypeOf(B.prototype, A.prototype);</p>
    <p>// B 继承 A 的静态属性</p>
    <p>Object.setPrototypeOf(B, A);</p>
    <p>const b = new B();</p>
    <p>《对象的扩展》一章给出过Object.setPrototypeOf方法的实现。</p>
    <p>Object.setPrototypeOf = function (obj, proto) {</p>
    <p>    obj.__proto__ = proto;</p>
    <p>    return obj;</p>
    <p>}</p>
    <p>因此，就得到了上面的结果。</p>
    <p>Object.setPrototypeOf(B.prototype, A.prototype);</p>
    <p>// 等同于</p>
    <p>B.prototype.__proto__ = A.prototype;</p>
    <p>Object.setPrototypeOf(B, A);</p>
    <p>// 等同于</p>
    <p>B.__proto__ = A;</p>
    <p>这两条继承链，可以这样理解：作为一个对象，子类（B）的原型（__proto__属性）是父类（A）；作为一个构造函数，子类（B）的原型对象（prototype属性）是父类的原型对象（prototype属性）的实例。</p>
    <p>Object.create(A.prototype);</p>
    <p>// 等同于</p>
    <p>B.prototype.__proto__ = A.prototype;</p>
    <p>extends关键字后面可以跟多种类型的值。</p>
    <p>class B extends A {</p>
    <p>}</p>
    <p>上面代码的A，只要是一个有prototype属性的函数，就能被B继承。由于函数都有prototype属性（除了Function.prototype函数），因此A可以是任意函数。</p>
    <p>下面，讨论两种情况。第一种，子类继承Object类。</p>
    <p>class A extends Object {</p>
    <p>}</p>
    <p>A.__proto__ === Object // true</p>
    <p>A.prototype.__proto__ === Object.prototype // true</p>
    <p>这种情况下，A其实就是构造函数Object的复制，A的实例就是Object的实例。</p>
    <p>第二种情况，不存在任何继承。</p>
    <p>class A {</p>
    <p>}</p>
    <p>A.__proto__ === Function.prototype // true</p>
    <p>A.prototype.__proto__ === Object.prototype // true</p>
    <p>这种情况下，A作为一个基类（即不存在任何继承），就是一个普通函数，所以直接继承Function.prototype。但是，A调用后返回一个空对象（即Object实例），所以A.prototype.__proto__指向构造函数（Object）的prototype属性。</p>
    <p>实例的 __proto__ 属性</p>
    <p>子类实例的__proto__属性的__proto__属性，指向父类实例的__proto__属性。也就是说，子类的原型的原型，是父类的原型。</p>
    <p>var p1 = new Point(2, 3);</p>
    <p>var p2 = new ColorPoint(2, 3, 'red');</p>
    <p>p2.__proto__ === p1.__proto__ // false</p>
    <p>p2.__proto__.__proto__ === p1.__proto__ // true</p>
    <p>上面代码中，ColorPoint继承了Point，导致前者原型的原型是后者的原型。</p>
    <p>因此，通过子类实例的__proto__.__proto__属性，可以修改父类实例的行为。</p>
    <p>p2.__proto__.__proto__.printName = function () {</p>
    <p>    console.log('Ha');</p>
    <p>};</p>
    <p>p1.printName() // "Ha"</p>
    <p>上面代码在ColorPoint的实例p2上向Point类添加方法，结果影响到了Point的实例p1。</p>
    <p>原生构造函数的继承</p>
    <p>原生构造函数是指语言内置的构造函数，通常用来生成数据结构。ECMAScript 的原生构造函数大致有下面这些。</p>
    <p>Boolean()</p>
    <p>Number()</p>
    <p>String()</p>
    <p>Array()</p>
    <p>Date()</p>
    <p>Function()</p>
    <p>RegExp()</p>
    <p>Error()</p>
    <p>Object()</p>
    <p>以前，这些原生构造函数是无法继承的，比如，不能自己定义一个Array的子类。</p>
    <p>function MyArray() {</p>
    <p>    Array.apply(this, arguments);</p>
    <p>}</p>
    <p>MyArray.prototype = Object.create(Array.prototype, {</p>
    <p>    constructor: {</p>
    <p>        value: MyArray,</p>
    <p>        writable: true,</p>
    <p>        configurable: true,</p>
    <p>        enumerable: true</p>
    <p>    }</p>
    <p>});</p>
    <p>上面代码定义了一个继承 Array 的MyArray类。但是，这个类的行为与Array完全不一致。</p>
    <p>var colors = new MyArray();</p>
    <p>colors[0] = "red";</p>
    <p>colors.length    // 0</p>
    <p>colors.length = 0;</p>
    <p>colors[0]    // "red"</p>
    <p>之所以会发生这种情况，是因为子类无法获得原生构造函数的内部属性，通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this，也就是说，原生构造函数的this无法绑定，导致拿不到内部属性。</p>
    <p>ES5 是先新建子类的实例对象this，再将父类的属性添加到子类上，由于父类的内部属性无法获取，导致无法继承原生的构造函数。比如，Array构造函数有一个内部属性[[DefineOwnProperty]]，用来定义新属性时，更新length属性，这个内部属性无法在子类获取，导致子类的length属性行为不正常。</p>
    <p>下面的例子中，我们想让一个普通对象继承Error对象。</p>
    <p>var e = {};</p>
    <p>Object.getOwnPropertyNames(Error.call(e))</p>
    <p>// [ 'stack' ]</p>
    <p>Object.getOwnPropertyNames(e)</p>
    <p>// []</p>
    <p>上面代码中，我们想通过Error.call(e)这种写法，让普通对象e具有Error对象的实例属性。但是，Error.call()完全忽略传入的第一个参数，而是返回一个新对象，e本身没有任何变化。这证明了Error.call(e)这种写法，无法继承原生构造函数。</p>
    <p>ES6 允许继承原生构造函数定义子类，因为 ES6 是先新建父类的实例对象this，然后再用子类的构造函数修饰this，使得父类的所有行为都可以继承。下面是一个继承Array的例子。</p>
    <p>class MyArray extends Array {</p>
    <p>    constructor(...args) {</p>
    <p>        super(...args);</p>
    <p>    }</p>
    <p>}</p>
    <p>var arr = new MyArray();</p>
    <p>arr[0] = 12;</p>
    <p>arr.length // 1</p>
    <p>arr.length = 0;</p>
    <p>arr[0] // undefined</p>
    <p>上面代码定义了一个MyArray类，继承了Array构造函数，因此就可以从MyArray生成数组的实例。这意味着，ES6 可以自定义原生数据结构（比如Array、String等）的子类，这是 ES5 无法做到的。</p>
    <p>上面这个例子也说明，extends关键字不仅可以用来继承类，还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上，定义自己的数据结构。下面就是定义了一个带版本功能的数组。</p>
    <p>class VersionedArray extends Array {</p>
    <p>    constructor() {</p>
    <p>        super();</p>
    <p>        this.history = [[]];</p>
    <p>    }</p>
    <p>    commit() {</p>
    <p>        this.history.push(this.slice());</p>
    <p>    }</p>
    <p>    revert() {</p>
    <p>        this.splice(0, this.length, ...this.history[this.history.length - 1]);</p>
    <p>    }</p>
    <p>}</p>
    <p>var x = new VersionedArray();</p>
    <p>x.push(1);</p>
    <p>x.push(2);</p>
    <p>x // [1, 2]</p>
    <p>x.history // [[]]</p>
    <p>x.commit();</p>
    <p>x.history // [[], [1, 2]]</p>
    <p>x.push(3);</p>
    <p>x // [1, 2, 3]</p>
    <p>x.history // [[], [1, 2]]</p>
    <p>x.revert();</p>
    <p>x // [1, 2]</p>
    <p>上面代码中，VersionedArray会通过commit方法，将自己的当前状态生成一个版本快照，存入history属性。revert方法用来将数组重置为最新一次保存的版本。除此之外，VersionedArray依然是一个普通数组，所有原生的数组方法都可以在它上面调用。</p>
    <p>下面是一个自定义Error子类的例子，可以用来定制报错时的行为。</p>
    <p>class ExtendableError extends Error {</p>
    <p>    constructor(message) {</p>
    <p>        super();</p>
    <p>        this.message = message;</p>
    <p>        this.stack = (new Error()).stack;</p>
    <p>        this.name = this.constructor.name;</p>
    <p>    }</p>
    <p>}</p>
    <p>class MyError extends ExtendableError {</p>
    <p>    constructor(m) {</p>
    <p>        super(m);</p>
    <p>    }</p>
    <p>}</p>
    <p>var myerror = new MyError('ll');</p>
    <p>myerror.message // "ll"</p>
    <p>myerror instanceof Error // true</p>
    <p>myerror.name // "MyError"</p>
    <p>myerror.stack</p>
    <p>// Error</p>
    <p>//         at MyError.ExtendableError</p>
    <p>//         ...</p>
    <p>注意，继承Object的子类，有一个行为差异。</p>
    <p>class NewObj extends Object{</p>
    <p>    constructor(){</p>
    <p>        super(...arguments);</p>
    <p>    }</p>
    <p>}</p>
    <p>var o = new NewObj({attr: true});</p>
    <p>o.attr === true    // false</p>
    <p>上面代码中，NewObj继承了Object，但是无法通过super方法向父类Object传参。这是因为 ES6 改变了Object构造函数的行为，一旦发现Object方法不是通过new Object()这种形式调用，ES6 规定Object构造函数会忽略参数。</p>
    <p>Mixin 模式的实现</p>
    <p>Mixin 指的是多个对象合成一个新的对象，新对象具有各个组成成员的接口。它的最简单实现如下。</p>
    <p>const a = {</p>
    <p>    a: 'a'</p>
    <p>};</p>
    <p>const b = {</p>
    <p>    b: 'b'</p>
    <p>};</p>
    <p>const c = {...a, ...b}; // {a: 'a', b: 'b'}</p>
    <p>上面代码中，c对象是a对象和b对象的合成，具有两者的接口。</p>
    <p>下面是一个更完备的实现，将多个类的接口“混入”（mix in）另一个类。</p>
    <p>function mix(...mixins) {</p>
    <p>    class Mix {</p>
    <p>        constructor() {</p>
    <p>            for (let mixin of mixins) {</p>
    <p>                copyProperties(this, new mixin()); // 拷贝实例属性</p>
    <p>            }</p>
    <p>        }</p>
    <p>    }</p>
    <p>    for (let mixin of mixins) {</p>
    <p>        copyProperties(Mix, mixin); // 拷贝静态属性</p>
    <p>        copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性</p>
    <p>    }</p>
    <p>    return Mix;</p>
    <p>}</p>
    <p>function copyProperties(target, source) {</p>
    <p>    for (let key of Reflect.ownKeys(source)) {</p>
    <p>        if ( key !== 'constructor'</p>
    <p>            && key !== 'prototype'</p>
    <p>            && key !== 'name'</p>
    <p>        ) {</p>
    <p>            let desc = Object.getOwnPropertyDescriptor(source, key);</p>
    <p>            Object.defineProperty(target, key, desc);</p>
    <p>        }</p>
    <p>    }</p>
    <p>}</p>
    <p>上面代码的mix函数，可以将多个对象合成为一个类。使用的时候，只要继承这个类即可。</p>
    <p>class DistributedEdit extends mix(Loggable, Serializable) {</p>
    <p>    // ...</p>
    <p>}</p>
</div>
